<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>无限画布追踪与全景生成</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
      font-family: sans-serif;
    }
    /* 控制面板，固定在左上角 */
    #controlPanel {
      position: fixed;
      top: 10px;
      left: 10px;
      background: rgba(255, 255, 255, 0.8);
      padding: 10px;
      border-radius: 4px;
      z-index: 100;
    }
    #controlPanel button,
    #controlPanel input {
      margin: 5px;
    }
    canvas {
      display: block;
      background: #f0f0f0;
    }
  </style>
</head>
<body>
  <div id="controlPanel">
    <button id="startBtn">开始</button>
    <button id="resetBtn">重置</button>
    <button id="generateBtn">生成轨迹</button>
    <br>
    <label for="speedSlider">速度倍率: <span id="speedValue">1</span></label>
    <input type="range" id="speedSlider" min="0.1" max="5" step="0.1" value="1">
  </div>
  <canvas id="canvas"></canvas>
  <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    // 虚拟坐标，初始设为 (0,0)，该点将在屏幕中点显示
    let posX = 0, posY = 0;
    // 用数组记录所有轨迹点（虚拟坐标）
    let track = [];

    // 运动变量：速度、角度
    let velocity = 0;
    // 初始方向默认向上（-90°）
    let angle = -Math.PI / 2;
    let rawAngle = angle; // 传感器采集的目标角度
    const defaultAngle = -Math.PI / 2;
    let initialAlpha = null; // 用于 deviceorientation 校准

    const accelerationThreshold = 0.2;
    let currentAcc = { x: 0, y: 0 };

    // 速度倍率由右侧滑块控制
    let speedMultiplier = 1;
    const speedSlider = document.getElementById('speedSlider');
    const speedValue = document.getElementById('speedValue');
    speedSlider.addEventListener('input', function() {
      speedMultiplier = parseFloat(this.value);
      speedValue.textContent = this.value;
    });

    // 控制按钮
    const startBtn = document.getElementById('startBtn');
    const resetBtn = document.getElementById('resetBtn');
    const generateBtn = document.getElementById('generateBtn');

    // 跟踪状态标志与动画帧 ID
    let trackingActive = false;
    let animationFrameId;

    // 如果需要，针对 iOS 13+ 请求传感器权限
    if (typeof DeviceMotionEvent !== 'undefined' &&
        typeof DeviceMotionEvent.requestPermission === 'function') {
      DeviceMotionEvent.requestPermission().catch(console.error);
    }

    // 监听 deviceorientation 事件：获取设备朝向
    window.addEventListener('deviceorientation', function(event) {
      if (event.alpha !== null) {
        if (initialAlpha === null) {
          initialAlpha = event.alpha;
        }
        // 计算目标角度：初始 alpha 与当前 alpha 的差，再加上默认初始方向
        rawAngle = (initialAlpha - event.alpha) * (Math.PI / 180) + defaultAngle;
      }
    });

    // 监听 devicemotion 事件：获取不含重力的加速度数据
    window.addEventListener('devicemotion', function(event) {
      if (event.acceleration) {
        currentAcc.x = event.acceleration.x || 0;
        currentAcc.y = event.acceleration.y || 0;
      }
    });

    // 动画参数
    const stepFactor = 2;         // 放大每帧位移，减少走动时忽左忽右的抖动
    const smoothingFactor = 0.1;    // 角度平滑更新参数

    function animate(timestamp) {
      if (!trackingActive) return;  // 若未处于记录状态则退出
      if (!animate.lastTime) {
        animate.lastTime = timestamp;
      }
      let deltaTime = (timestamp - animate.lastTime) / 1000;
      animate.lastTime = timestamp;

      // 平滑更新当前角度
      angle += (rawAngle - angle) * smoothingFactor;

      // 根据加速度更新速度
      const totalAcc = Math.sqrt(currentAcc.x ** 2 + currentAcc.y ** 2);
      if (totalAcc > accelerationThreshold) {
        velocity += totalAcc * deltaTime * 0.5 * speedMultiplier;
        if (velocity > 5) velocity = 5;
      } else {
        velocity *= 0.8;
        if (velocity < 0.01) velocity = 0;
      }

      // 计算位移
      const dx = velocity * Math.cos(angle) * stepFactor;
      const dy = velocity * Math.sin(angle) * stepFactor;
      posX += dx;
      posY += dy;

      // 保存当前点到轨迹数组
      track.push({ x: posX, y: posY });

      redrawTracking();

      animationFrameId = requestAnimationFrame(animate);
    }

    // 绘制当前跟踪状态下的画面：使当前点始终位于屏幕中央
    function redrawTracking() {
      const offsetX = canvas.width / 2 - posX;
      const offsetY = canvas.height / 2 - posY;
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      if (track.length > 0) {
        ctx.beginPath();
        ctx.moveTo(track[0].x + offsetX, track[0].y + offsetY);
        for (let i = 1; i < track.length; i++) {
          ctx.lineTo(track[i].x + offsetX, track[i].y + offsetY);
        }
        ctx.strokeStyle = "red";
        ctx.lineWidth = 2;
        ctx.stroke();
      }
      // 绘制当前位置的标识
      ctx.beginPath();
      ctx.arc(posX + offsetX, posY + offsetY, 5, 0, 2 * Math.PI);
      ctx.fillStyle = "blue";
      ctx.fill();
    }

    // 生成全景图：计算所有轨迹点的边界，按比例缩放后在当前画布上显示
    function generatePanorama() {
      if (track.length === 0) return;
      // 计算轨迹边界
      let minX = track[0].x, maxX = track[0].x;
      let minY = track[0].y, maxY = track[0].y;
      for (let p of track) {
        if (p.x < minX) minX = p.x;
        if (p.x > maxX) maxX = p.x;
        if (p.y < minY) minY = p.y;
        if (p.y > maxY) maxY = p.y;
      }
      const trackWidth = maxX - minX;
      const trackHeight = maxY - minY;

      // 计算缩放比例及偏移，使轨迹完整显示在一屏（预留边距）
      const margin = 20;
      const scaleX = (canvas.width - margin * 2) / trackWidth;
      const scaleY = (canvas.height - margin * 2) / trackHeight;
      const scale = Math.min(scaleX, scaleY);
      const offsetX = (canvas.width - trackWidth * scale) / 2 - minX * scale;
      const offsetY = (canvas.height - trackHeight * scale) / 2 - minY * scale;

      // 清除画布，绘制全景图
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.beginPath();
      ctx.moveTo(track[0].x * scale + offsetX, track[0].y * scale + offsetY);
      for (let i = 1; i < track.length; i++) {
        ctx.lineTo(track[i].x * scale + offsetX, track[i].y * scale + offsetY);
      }
      ctx.strokeStyle = "red";
      ctx.lineWidth = 2;
      ctx.stroke();

      // 绘制起点（蓝色）和终点（绿色）
      ctx.fillStyle = "blue";
      ctx.beginPath();
      ctx.arc(track[0].x * scale + offsetX, track[0].y * scale + offsetY, 5, 0, 2 * Math.PI);
      ctx.fill();
      ctx.fillStyle = "green";
      ctx.beginPath();
      ctx.arc(track[track.length - 1].x * scale + offsetX, track[track.length - 1].y * scale + offsetY, 5, 0, 2 * Math.PI);
      ctx.fill();
    }

    // 开始按钮：启动运动记录
    startBtn.addEventListener('click', function() {
      if (!trackingActive) {
        trackingActive = true;
        animate.lastTime = null;
        animationFrameId = requestAnimationFrame(animate);
      }
    });

    // 重置按钮：停止记录并清空所有数据
    resetBtn.addEventListener('click', function() {
      trackingActive = false;
      cancelAnimationFrame(animationFrameId);
      posX = 0;
      posY = 0;
      velocity = 0;
      track = [];
      initialAlpha = null;
      ctx.clearRect(0, 0, canvas.width, canvas.height);
    });

    // 生成轨迹按钮：停止记录并生成全景图
    generateBtn.addEventListener('click', function() {
      trackingActive = false;
      cancelAnimationFrame(animationFrameId);
      generatePanorama();
    });
  </script>
</body>
</html>